package com.tasm.aop.framework;

import com.tasm.aop.ASMEngine;
import com.tasm.aop.annotation.EventTrack;
import com.tasm.aop.annotation.SingleClick;
import com.tasm.aop.plugins.AopPointcut;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.AdviceAdapter;

import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;


/**
 * @author viyu
 * @desc AOP method visitor，主要做的就是在方法中插入了AopInvoker的代码
 */
class ASMMethodVisitor extends AdviceAdapter {

    private int mInvokerVarIndex = 0;

    private final String mClassName;
    private final String mMethodName;

    private Map<String, ASMAnnotationVisitor> mAnnotationMethod;
    public ASMMethodVisitor(int api, MethodVisitor originMV, int access, String desc, String className, String methodName) {
        super(api, originMV, access, methodName, desc);
        mClassName = className;
        mMethodName = methodName;

    }

    @Override
    public AnnotationVisitor visitAnnotation(String path, boolean visible) {
        /**
         * path=Lcom/viyu/demo/testclass/EventTrack;
         * 需要将它替换成：com.viyu.demo.testclass.EventTrack
         */
        String key = path.replace("/", ".").substring(1, path.length() - 1);

        ASMAnnotationVisitor asmAnnotationVisitor = new ASMAnnotationVisitor(api, key, super.visitAnnotation(path, visible));
        if(ASMEngine.mAnnotationList.contains(key)) {
            if(mAnnotationMethod == null) {
                mAnnotationMethod = new HashMap<>();
            }
            mAnnotationMethod.put(key, asmAnnotationVisitor);
        }
        return asmAnnotationVisitor;
    }

    @Override
    protected void onMethodEnter() {
        super.onMethodEnter();
        beginAspect();
    }

    @Override
    protected void onMethodExit(int opcode) {
        super.onMethodExit(opcode);
        afterAspect();
    }

    @Override
    public void visitMaxs(int maxStack, int maxLocals) {
        super.visitMaxs(maxStack + 2, maxLocals + 1);
    }

    /**
     * 在方法开始插入AopInvoker.aspectBeforeInvoke()
     */
    private void beginAspect() {
        if (mv == null || mAnnotationMethod == null || mAnnotationMethod.isEmpty()) {
            return;
        }
        mAnnotationMethod.forEach(new BiConsumer<String, ASMAnnotationVisitor>() {
            @Override
            public void accept(String key, ASMAnnotationVisitor asmAnnotationVisitor) {mv.visitLdcInsn(mClassName);
                mv.visitLdcInsn(mClassName);
                mv.visitLdcInsn(mMethodName);
                mv.visitLdcInsn(key);

//                if (key.equals(InsertLog.class.getName())) {
//                    mv.visitMethodInsn(INVOKESTATIC, "com/tasm/aop/plugins/AopPointcut", "newInvokerLog", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/tasm/aop/plugins/AopPointcut;", false);
//                } else if(key.equals(EventTrack.class.getName())){
//                    mv.visitLdcInsn((int) AopPointcut.annotationParam.get(key + "_what"));
//                    mv.visitLdcInsn((String) AopPointcut.annotationParam.get(key + "_value1"));
//                    mv.visitLdcInsn((String) AopPointcut.annotationParam.get(key + "_value2"));
//                    mv.visitMethodInsn(INVOKESTATIC, "com/tasm/aop/plugins/AopPointcut", "newInvokerEventTrack", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;)Lcom/tasm/aop/plugins/AopPointcut;", false);
//                } else if(key.equals(SingleClick.class.getName())){
//                    mv.visitLdcInsn((long) AopPointcut.annotationParam.get(key + "_value"));
//                    mv.visitMethodInsn(INVOKESTATIC, "com/tasm/aop/plugins/AopPointcut", "newInvokerSingleClick", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;)Lcom/tasm/aop/plugins/AopPointcut;", false);
//                }

                if(key.equals(SingleClick.class.getName())){
                    mv.visitLdcInsn((long) AopPointcut.annotationParam.get(key + "_value"));
                    mv.visitMethodInsn(INVOKESTATIC, "com/tasm/aop/plugins/AopPointcut", "newInvokerSingleClick", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;J)Lcom/tasm/aop/plugins/AopPointcut;", false);
                } else if(key.equals(EventTrack.class.getName())){
                    mv.visitLdcInsn((int) AopPointcut.annotationParam.get(key + "_what"));
                    mv.visitLdcInsn((String) AopPointcut.annotationParam.get(key + "_value1"));
                    mv.visitLdcInsn((String) AopPointcut.annotationParam.get(key + "_value2"));
                    mv.visitMethodInsn(INVOKESTATIC, "com/tasm/aop/plugins/AopPointcut", "newInvokerEventTrack", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)Lcom/tasm/aop/plugins/AopPointcut;", false);
                } else {
                    mv.visitMethodInsn(INVOKESTATIC, "com/tasm/aop/plugins/AopPointcut", "newInvoker", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/tasm/aop/plugins/AopPointcut;", false);
                }

                mv.visitInsn(DUP);
                mv.visitMethodInsn(INVOKEVIRTUAL, "com/tasm/aop/plugins/AopPointcut", "aspectBeforeInvoke", "()V", false);
                mInvokerVarIndex = newLocal(Type.getType("Lcom/tasm/aop/plugins/AopPointcut;"));
                mv.visitVarInsn(ASTORE, mInvokerVarIndex);
            }
        });

    }

    /**
     * 在方法结束插入AopInvoker.aspectAfterInvoke()
     */
    private void afterAspect() {
        if (mv == null || mAnnotationMethod == null || mAnnotationMethod.isEmpty()) {
            return;
        }
        mv.visitVarInsn(ALOAD, mInvokerVarIndex);
        mv.visitMethodInsn(INVOKEVIRTUAL, "com/tasm/aop/plugins/AopPointcut", "aspectAfterInvoke", "()V", false);
    }
}
